#pragma once

#include <iostream>
#include <queue>
#include <string>
#include <pthread.h>

const int defaultcap = 5;

template <typename T>
class BlockQueue
{
private:
    bool IsFull()
    {
        return _block_queue.size() == _max_cap;
    }

    bool IsEmpty()
    {
        return _block_queue.empty();
    }

public:
    BlockQueue(const int cap = defaultcap) : _max_cap(cap)
    {
        // 局部锁
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_p_cond, nullptr);
        pthread_cond_init(&_c_cond, nullptr);
    }

    // 假设：1个生产者，2个消费者，此时只有一个商品
    // 在唤醒消费者时全部唤醒了，两者竞争时只能有一个拿到锁，然后消费，释放锁。
    // 那另一个在干什么呢？ 在锁那里等待，不是在条件变量下等待! 只要那个一释放锁，这个就可以拿到。
    // 但是此时队列已经为空了，像这种添加尚未满足，但线程被异常唤醒的情况，叫做 伪唤醒。

    // 解决方法：在判断是否已满/空时，循环检查。保证代码的健壮性
    void Pop(T *out)
    {
        pthread_mutex_lock(&_mutex);
        while (IsEmpty())
        {
            // 队列空了，也要等待
            pthread_cond_wait(&_c_cond, &_mutex);
        }
        // 不是空的 || 被唤醒了
        *out = _block_queue.front();
        _block_queue.pop();

        pthread_mutex_unlock(&_mutex);
        pthread_cond_signal(&_p_cond); // 消费了，叫醒生产者进行生产
    }
    void Equeue(const T &in)
    {
        pthread_mutex_lock(&_mutex);
        while (IsFull())
        {
            // 队列满了，生产者不能生产了，必须等待

            // 现在还在临界区里面，还没解锁呢，如果在等待时又没解锁，
            // 那消费者pop的时候也拿不到锁了，造成死锁!
            // phread_cond_wait函数：
            // 在被调用的时：除了让自己排队等待，还会自己释放传入的锁
            // 在被唤醒返回的时：为了避免没有锁也还在临界区里的问题，
            // 必须先参与锁的竞争!
            pthread_cond_wait(&_p_cond, &_mutex);
        }
        // 没满 || 被唤醒了
        _block_queue.push(in); // 生产到了阻塞队列

        pthread_mutex_unlock(&_mutex);
        pthread_cond_signal(&_c_cond); // 生产了，就叫醒消费者进行消费
    }
    ~BlockQueue()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_p_cond);
        pthread_cond_destroy(&_c_cond);
    }

private:
    std::queue<T> _block_queue; // 临界资源
    int _max_cap;               // 最大容量
    pthread_mutex_t _mutex;
    pthread_cond_t _p_cond; // 生产者等待条件变量
    pthread_cond_t _c_cond; // 消费者等待条件变量
};